package com.tcy.game.angryrobots.sprite;

import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.tcy.game.angryrobots.Assets;
import com.tcy.game.angryrobots.World;
import com.tcy.game.angryrobots.general.Config;

import static com.tcy.game.angryrobots.general.MathUtils.*;
import static jdk.nashorn.internal.objects.NativeMath.random;

/**
 * Robot
 * Created by 80002023 on 2016/7/27.
 */
public class Robot extends GameObject {

    public static final int SCANNING = INACTIVE + 1;
    public static final int WALKING_RIGHT = SCANNING + 1;
    public static final int WALKING_RIGHT_DIAGONAL = WALKING_RIGHT + 1;
    public static final int WALKING_LEFT = WALKING_RIGHT_DIAGONAL + 1;
    public static final int WALKING_LEFT_DIAGONAL = WALKING_LEFT + 1;
    public static final int WALKING_DOWN = WALKING_LEFT_DIAGONAL + 1;
    public static final int WALKING_UP = WALKING_DOWN + 1;

    private static final float WALKING_SPEED = Config.asFloat("Robot.speed", 1.25f);

    private GameObject player;
    private Array<Rectangle> walls;
    private final float distance;
    private final float fudge;
    private World.FireCommand fireCommand;
    private Vector2 firingDirection;
    private Vector2 robotPos;
    private Vector2 playerPos;
    private Vector2 lineStart;
    private Vector2 lineEnd;
    private float respawnX;
    private float respawnY;

    public Robot() {
        width = Assets.robotWidth;
        height = Assets.robotHeight;
        distance = max(width, height);
        fudge = distance * 0.25f;
        setState(INACTIVE);
        firingDirection = new Vector2();
        robotPos = new Vector2();
        playerPos = new Vector2();
        lineStart = new Vector2();
        lineEnd = new Vector2();
    }

    public void setPlayer(GameObject player) {
        this.player = player;
    }

    public void setWalls(Array<Rectangle> walls) {
        this.walls = walls;
    }

    public void setFireCommand(World.FireCommand fireCommand) {
        this.fireCommand = fireCommand;
    }

    @Override
    public void update(float delta) {
        stateTime += delta;
        moveRobot(delta);
        if (fireCommand != null && canFire(delta) && canSeePlayer()) {
            firingDirection.set(player.x - x, player.y - y);
            firingDirection.nor();
            fireCommand.fire(this, firingDirection.x, firingDirection.y);
        }
    }

    private boolean canFire(float delta) {
        // TODO: remove magic numbers, or possibly switch to expovariate randomness.
        return random(100) < 50 * delta;
    }

    private void moveRobot(float delta) {
        float dx = (player.x + player.width / 2) - (x + width / 2);
        float dy = (player.y + player.height / 2) - (y + height / 2);
        dx = abs(dx) >= 2 ? sgn(dx) : 0.0f;
        dy = abs(dy) >= 2 ? sgn(dy) : 0.0f;
        float ax = 0.0f;
        float ay = 0.0f;
        if (!wouldHitWall(dx, dy)) {
            ax = dx;
            ay = dy;
        } else if (dx != 0 && !wouldHitWall(dx, 0)) {
            ax = dx;
        } else if (dy != 0 && !wouldHitWall(0, dy)) {
            ay = dy;
        }
        dx = ax * WALKING_SPEED;
        dy = ay * WALKING_SPEED;
        x += dx * delta;
        y += dy * delta;

        int newState = getMovementState(dx, dy);
        if (newState != state) {
            setState(newState);
        }
    }

    private int getMovementState(float dx, float dy) {
        if (dx == 0.0f && dy == 0.0f) {
            return SCANNING;
        } else if (dx > 0) {
            return (dy == 0) ? WALKING_RIGHT : WALKING_RIGHT_DIAGONAL;
        } else if (dx < 0) {
            return (dy == 0) ? WALKING_LEFT : WALKING_LEFT_DIAGONAL;
        } else if (dy < 0) {
            return WALKING_DOWN;
        } else {
            return WALKING_UP;
        }
    }

    private boolean wouldHitWall(float dx, float dy) {
        float x1 = x + width / 2;
        float y1 = y + height / 2;
        float x2 = x1 + dx * distance;
        float y2 = y1 + dy * distance;

        for (int i = 0; i < walls.size; i++) {
            Rectangle wall = walls.get(i);
            if (doesLineHitWall(wall, x1, y1, x2, y2)) {
                return true;
            }
        }
        return false;
    }

    private boolean doesLineHitWall(Rectangle rect, float x1, float y1, float x2, float y2) {
        // Does not intersect if minimum y coordinate is below the rectangle.
        float minY = min(y1, y2);
        if (minY >= rect.y + rect.height + fudge) return false;

        // Does not intersect if maximum y coordinate is above the rectangle.
        float maxY = max(y1, y2);
        if (maxY < rect.y - fudge) return false;

        // Does not intersect if minimum x coordinate is to the right of the rectangle.
        float minX = min(x1, x2);
        if (minX >= rect.x + rect.width + fudge) return false;

        // Does not intersect if maximum x coordinate is to the left of the rectangle.
        float maxX = max(x1, x2);
        if (maxX < rect.x - fudge) return false;

        // And that's good enough, because the robots need to be a bit stupid
        // when they're near the ends of walls.
        return true;
    }

    private boolean canSeePlayer() {
        return hasLineOfSight(this, player);
    }

    private boolean hasLineOfSight(GameObject a, GameObject b) {
        return hasLineOfSight(a.x + a.width / 2, a.y + a.height / 2, b.x + b.width / 2, b.y + b.height / 2);
    }

    private boolean hasLineOfSight(float x1, float y1, float x2, float y2) {
        robotPos.set(x1, y1);
        playerPos.set(x2, y2);
        for (int i = 0; i < walls.size; i++) {
            Rectangle wall = walls.get(i);
            if (wall.width > wall.height) {
                lineStart.set(wall.x, wall.y + wall.height / 2);
                lineEnd.set(wall.x + wall.width, lineStart.y);
            } else {
                lineStart.set(wall.x + wall.width / 2, wall.y);
                lineEnd.set(lineStart.x, wall.y + wall.height);
            }
            if (Intersector.intersectSegments(robotPos, playerPos, lineStart, lineEnd, null)) {
                return false;
            }
        }
        return true;
    }

    public void setRespawnPoint(float x, float y) {
        respawnX = x;
        respawnY = y;
    }

    public void respawn() {
        x = respawnX;
        y = respawnY;
    }
}
